package work.linruchang.oauthresourceproject.controller;

import cn.hutool.core.collection.ConcurrentHashSet;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;
import work.linruchang.oauthresourceproject.bean.SysUser;
import work.linruchang.oauthresourceproject.bean.ThirdSystemInfo;
import work.linruchang.oauthresourceproject.config.oauth.CacheInfo;
import work.linruchang.oauthresourceproject.config.oauth.*;
import work.linruchang.oauthresourceproject.config.constant.RequestEnum;
import work.linruchang.oauthresourceproject.config.constant.SysetmBaseCustomException;
import work.linruchang.oauthresourceproject.service.SysUserService;
import work.linruchang.oauthresourceproject.service.ThirdSystemInfoService;

import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.List;

/**
 * Oauth认证控制器相关接口
 *
 * @author LinRuChang
 * @version 1.0
 * @date 2022/08/19
 * @since 1.8
 **/
@RequestMapping("oauth")
@Controller
public class OauthController {

    @Autowired
    ThirdSystemInfoService thirdSystemInfoService;
    @Autowired
    SysUserService sysUserService;

    /**
     * 获取第三方应用的信息，如客户端的ID、密钥、回调URL
     * <p>
     * 类似码云的 设置=》第三方应用
     *
     * @return
     */
    @GetMapping("third-system-infos")
    @ResponseBody
    public List<ThirdSystemInfo> thirdSystemInfos() {
        return thirdSystemInfoService.list(Wrappers.lambdaQuery(ThirdSystemInfo.class)
                .eq(ThirdSystemInfo::getUserName, ThreadLocalRequestInfo.getUserName()));
    }

    /**
     * 删除第三方客户端信息
     *
     * @param id 主键ID
     * @return
     */
    @DeleteMapping("third-system-infos/{id}")
    @ResponseBody
    public boolean deleteThirdSystemInfos(@PathVariable String id) {
        return thirdSystemInfoService.removeById(id);
    }

    @GetMapping("third-system-infos/delete/{id}")
    @ResponseBody
    public boolean deleteThirdSystemInfos2(@PathVariable String id) {
        return deleteThirdSystemInfos(id);
    }


    /**
     * 新增第三方客户端信息
     *
     * @param thirdSystemInfo 第三方客户端信息
     * @return
     */
    @PostMapping("third-system-infos/add")
    @ResponseBody
    public boolean addThirdSystemInfos(@RequestBody ThirdSystemInfo thirdSystemInfo) {
        thirdSystemInfo.setId(StrUtil.blankToDefault(thirdSystemInfo.getId(), UUID.randomUUID().toString(true)));
        thirdSystemInfo.setClientId(StrUtil.blankToDefault(thirdSystemInfo.getClientId(), UUID.randomUUID().toString(true)));
        thirdSystemInfo.setClientSecret(StrUtil.blankToDefault(thirdSystemInfo.getClientSecret(), UUID.randomUUID().toString(true)));
        thirdSystemInfo.setUserName(StrUtil.blankToDefault(thirdSystemInfo.getUserName(), ThreadLocalRequestInfo.getUserName()));
        return thirdSystemInfoService.save(thirdSystemInfo);
    }

    /**
     * 更新第三方客户端信息
     *
     * @param thirdSystemInfo 第三方客户端信息
     * @return
     */
    @PostMapping("third-system-infos/update")
    @ResponseBody
    public boolean updateThirdSystemInfos(@RequestBody ThirdSystemInfo thirdSystemInfo) {
        return thirdSystemInfoService.updateById(thirdSystemInfo);
    }

    /**
     * 授权第三方客户端
     *
     * @param authorizeInfo 授权信息
     * @return
     */
    @GetMapping("authorize")
    @ResponseBody
    public ModelAndView authorize(AuthorizeInfo authorizeInfo, ModelAndView modelAndView) {
        Assert.notBlank(authorizeInfo.getClient_id(), () -> new SysetmBaseCustomException(RequestEnum.ResponseCodeEnum.REQUEST_MISS_CONFIG));
        Assert.notBlank(authorizeInfo.getScope(), () -> new SysetmBaseCustomException(RequestEnum.ResponseCodeEnum.MISSING_AUTHORIZATION_RIGHTS));
        ThirdSystemInfo thirdSystemInfo = thirdSystemInfoService.getOne(Wrappers.lambdaQuery(ThirdSystemInfo.builder().clientId(authorizeInfo.getClient_id()).build()));
        Assert.notNull(thirdSystemInfo, () -> new SysetmBaseCustomException(RequestEnum.ResponseCodeEnum.INVALID_CLIENT));

        if (!StrUtil.equals(thirdSystemInfo.getClientCallbackUrl(), authorizeInfo.getRedirect_uri())) {
            Assert.equals(thirdSystemInfo.getClientCallbackUrl(), authorizeInfo.getRedirect_uri(), () -> new SysetmBaseCustomException(RequestEnum.ResponseCodeEnum.INCONSISTENT_PARAMETERS));
        }
        Assert.isTrue(StrUtil.equalsAnyIgnoreCase(authorizeInfo.getResponse_type(), ResponseType.RESPONSE_TYPE_CODE, ResponseType.RESPONSE_TYPE_TOKEN), () -> new SysetmBaseCustomException(RequestEnum.ResponseCodeEnum.INVALID_RESPONSE_TYPE));
        Assert.isTrue(StrUtil.equalsAnyIgnoreCase(authorizeInfo.getScope(), Scope.SCOPE_USER_INFO), () -> new SysetmBaseCustomException(RequestEnum.ResponseCodeEnum.INVALID_SCOPE));


        //内部定义的授权标识
        String oauthId = UUID.randomUUID().toString(true);
        CacheInfo.cacheAuthorizeInfo.put(oauthId, authorizeInfo);
        CacheInfo.cacheClientInfo.put(oauthId, thirdSystemInfo);

        modelAndView.addObject("authorizeInfo", authorizeInfo);
        modelAndView.addObject("thirdSystemInfo", thirdSystemInfo);
        modelAndView.addObject("oauthId", oauthId);
        modelAndView.setViewName("authorize");
        return modelAndView;
    }

    /**
     * 获取授权回调URL的地址
     *
     * @param oauthId
     * @return
     */
    @GetMapping("authorizeCallbackClientUrl")
    public String authorizeCallbackClientUrl(String oauthId, boolean refuseAuthFlag) {
        AuthorizeInfo authorizeInfo = CacheInfo.cacheAuthorizeInfo.get(oauthId);
        ThirdSystemInfo thirdSystemInfo = CacheInfo.cacheClientInfo.get(oauthId);
        Assert.noNullElements(new Object[]{thirdSystemInfo, authorizeInfo}, () -> new SysetmBaseCustomException(RequestEnum.ResponseCodeEnum.INVALID_PARAMETERS));

        String clientCallbackUrl = thirdSystemInfo.getClientCallbackUrl();
        Dict urlParams = null;


        //拒绝授权
        if (refuseAuthFlag) {
            urlParams = Dict.create()
                    .set("state", authorizeInfo.getState())
                    .set("error", "授权被用户拒绝");
        } else { //同意授权

            //授权码模式
            if (StrUtil.equals(authorizeInfo.getResponse_type(), ResponseType.RESPONSE_TYPE_CODE)) {

                String code = UUID.fastUUID().toString(true);
                ConcurrentHashSet<String> clientAuthCodes = CacheInfo.cacheClientAuthCode.get(thirdSystemInfo.getClientId());
                clientAuthCodes = ObjectUtil.defaultIfNull(clientAuthCodes, new ConcurrentHashSet<>());
                clientAuthCodes.add(code);
                CacheInfo.cacheClientAuthCode.put(thirdSystemInfo.getClientId(), clientAuthCodes);

                CacheInfo.cacheAuthCodeAllAuthInfo.put(code, new AllAuthInfo()
                        .setUserId(ThreadLocalRequestInfo.getUserId())
                        .setAuthorizeInfo(authorizeInfo)
                        .setThirdSystemInfo(thirdSystemInfo));

                urlParams = Dict.create()
                        .set("state", authorizeInfo.getState())
                        .set("code", code);

                //直接tokne模式
            } else if (StrUtil.equals(authorizeInfo.getResponse_type(), ResponseType.RESPONSE_TYPE_TOKEN)) {
                String accessToken = UUID.fastUUID().toString(true);
                TokenInfo tokenInfo = TokenInfo.builder().access_token(accessToken)
                        .expires_in(DateUnit.DAY.getMillis() / 1000)
                        .token_type(TokenType.token_type_bearer)
                        .build();
                urlParams = Dict.create().parseBean(tokenInfo, false, true)
                        .set("state", authorizeInfo.getState());

                tokenInfo.setCreateTime(new Date());
                AllAuthInfo allAuthInfo = new AllAuthInfo()
                        .setAuthorizeInfo(authorizeInfo)
                        .setThirdSystemInfo(thirdSystemInfo)
                        .setTokenInfo(tokenInfo)
                        .setUserId(ThreadLocalRequestInfo.getUserId());

                CacheInfo.cacheAccessTokenAllAuthInfo.put(accessToken, allAuthInfo);
            }
        }
        clientCallbackUrl = StrUtil.format("{}?{}", clientCallbackUrl, URLUtil.buildQuery(urlParams, StandardCharsets.UTF_8));

        CacheInfo.cacheAuthorizeInfo.remove(oauthId);
        CacheInfo.cacheClientInfo.remove(oauthId);
        return StrUtil.format("redirect:{}", clientCallbackUrl);
    }


    /**
     * 获取token
     * <p>
     * 获取新token需要的参数：grant_type、code
     * 刷新token需要的参数：grant_type、refresh_token
     *
     * @param authorizeInfo 认证信息
     * @return
     */
    @PostMapping("token")
    @ResponseBody
    public TokenInfo getToken(AuthorizeInfo authorizeInfo) {
        TokenInfo tokenInfo = null;

        //根据授权码获取token
        if (StrUtil.equalsIgnoreCase(GrantType.GRANT_TYPE_AUTHORIZATION_CODE, authorizeInfo.getGrant_type())) {

            String code = authorizeInfo.getCode();
            AllAuthInfo allAuthInfo = CacheInfo.cacheAuthCodeAllAuthInfo.get(code);
            Assert.notBlank(code, () -> new SysetmBaseCustomException(RequestEnum.ResponseCodeEnum.REQUEST_MISS_CONFIG));
            Assert.notNull(allAuthInfo, () -> new SysetmBaseCustomException(RequestEnum.ResponseCodeEnum.INVALID_AUTH_CODE));

            authorizeInfo.setClient_id(ThreadLocalRequestInfo.getClientId());
            authorizeInfo.setClient_secret(ThreadLocalRequestInfo.getClientSecret());
            ThirdSystemInfo thirdSystemInfo = allAuthInfo.getThirdSystemInfo();

            if (!StrUtil.equals(thirdSystemInfo.getClientCallbackUrl(), authorizeInfo.getRedirect_uri())) {
                Assert.equals(thirdSystemInfo.getClientCallbackUrl(), authorizeInfo.getRedirect_uri(), () -> new SysetmBaseCustomException(RequestEnum.ResponseCodeEnum.INCONSISTENT_PARAMETERS));
            }
            Assert.isTrue(StrUtil.equalsAnyIgnoreCase(authorizeInfo.getGrant_type(), GrantType.GRANT_TYPE_AUTHORIZATION_CODE, GrantType.GRANT_TYPE_IMPLICIT, GrantType.GRANT_TYPE_PASSWORD, GrantType.GRANT_TYPE_CLIENT_CREDENTIALS, GrantType.GRANT_TYPE_REFRESH_TOKEN), () -> new SysetmBaseCustomException(RequestEnum.ResponseCodeEnum.INVALID_GRANT_TYPE));


            String accessToken = UUID.fastUUID().toString(true);
            tokenInfo = TokenInfo.builder().access_token(accessToken)
                    .expires_in(DateUnit.DAY.getMillis() / 1000)
                    .token_type(TokenType.token_type_bearer)
                    .refresh_token(UUID.fastUUID().toString(true))
                    .expires_in_refresh(2 * (DateUnit.DAY.getMillis() / 1000))
                    .createTime(new Date())
                    .build();
            allAuthInfo.setTokenInfo(tokenInfo);
            CacheInfo.cacheAccessTokenAllAuthInfo.put(accessToken, allAuthInfo);
            CacheInfo.cacheAuthCodeAllAuthInfo.remove(code);
            //根据刷新token获取新的access_token以及refresh_token
        } else if (StrUtil.equalsIgnoreCase(GrantType.GRANT_TYPE_REFRESH_TOKEN, authorizeInfo.getGrant_type())) {

            AllAuthInfo findedAllAuthInfo = CacheInfo.cacheAccessTokenAllAuthInfo.values().stream()
                    .filter(currentAllAuthInfo -> StrUtil.equalsIgnoreCase(authorizeInfo.getRefresh_token(), currentAllAuthInfo.getTokenInfo().getRefresh_token()))
                    .findFirst()
                    .orElse(null);
            Assert.notNull(findedAllAuthInfo, () -> new SysetmBaseCustomException(RequestEnum.ResponseCodeEnum.INVALID_REFRESH_TOKEN));
            tokenInfo = findedAllAuthInfo.getTokenInfo();
            boolean refreshTokenOverdueFlag = DateUtil.compare(new Date(), DateUtil.offset(tokenInfo.getCreateTime(), DateField.SECOND, tokenInfo.getExpires_in_refresh().intValue())) > 0;
            Assert.isFalse(refreshTokenOverdueFlag, () -> new SysetmBaseCustomException(RequestEnum.ResponseCodeEnum.EXPIRED_REFRESH_TOKEN));

            //刷新token未过期，则生成新的token对，删除旧的token对
            CacheInfo.cacheAccessTokenAllAuthInfo.remove(tokenInfo.getAccess_token());
            String newAccessToken = UUID.fastUUID().toString(true);
            String newRefreshToken = UUID.fastUUID().toString(true);
            tokenInfo.setCreateTime(new Date());
            tokenInfo.setAccess_token(newAccessToken);
            tokenInfo.setRefresh_token(newRefreshToken);
            CacheInfo.cacheAccessTokenAllAuthInfo.put(newAccessToken, findedAllAuthInfo);

        } else if (StrUtil.equalsIgnoreCase(GrantType.GRANT_TYPE_PASSWORD, authorizeInfo.getGrant_type())) {
            SysUser userInfo = getUserInfo(authorizeInfo.getOauthUsername());
            Assert.notNull(userInfo,() -> new SysetmBaseCustomException(RequestEnum.ResponseCodeEnum.USERNAME_OR_PASSWORD_ERROR));
            Assert.equals(userInfo.getPassword(), authorizeInfo.getOauthPassword(),() -> new SysetmBaseCustomException(RequestEnum.ResponseCodeEnum.USERNAME_OR_PASSWORD_ERROR));
            String accessToken = UUID.fastUUID().toString(true);
            tokenInfo = TokenInfo.builder().access_token(accessToken)
                    .expires_in(DateUnit.DAY.getMillis() / 1000)
                    .token_type(TokenType.token_type_bearer)
                    .refresh_token(UUID.fastUUID().toString(true))
                    .expires_in_refresh(2 * (DateUnit.DAY.getMillis() / 1000))
                    .createTime(new Date())
                    .build();
            AllAuthInfo allAuthInfo = new AllAuthInfo();
            allAuthInfo.setTokenInfo(tokenInfo);
            allAuthInfo.setUserId(userInfo.getId());
            allAuthInfo.setThirdSystemInfo(thirdSystemInfoService.getById(ThreadLocalRequestInfo.getClientId()));
            allAuthInfo.setAuthorizeInfo(authorizeInfo);
            CacheInfo.cacheAccessTokenAllAuthInfo.put(accessToken, allAuthInfo);
        } else if (StrUtil.equalsIgnoreCase(GrantType.GRANT_TYPE_CLIENT_CREDENTIALS, authorizeInfo.getGrant_type())) {
            String accessToken = UUID.fastUUID().toString(true);
            tokenInfo = TokenInfo.builder().access_token(accessToken)
                    .expires_in(DateUnit.DAY.getMillis() / 1000)
                    .token_type(TokenType.token_type_bearer)
                    .createTime(new Date())
                    .build();
            AllAuthInfo allAuthInfo = new AllAuthInfo();
            allAuthInfo.setTokenInfo(tokenInfo);
            allAuthInfo.setThirdSystemInfo(thirdSystemInfoService.getById(ThreadLocalRequestInfo.getClientId()));
            allAuthInfo.setAuthorizeInfo(authorizeInfo);
            CacheInfo.cacheAccessTokenAllAuthInfo.put(accessToken, allAuthInfo);
        }
        Assert.notNull(tokenInfo, () -> new SysetmBaseCustomException(RequestEnum.ResponseCodeEnum.UNSUPPORT_GRANT_TYPE));
        return tokenInfo;
    }

    public SysUser getUserInfo(String userName) {
        return sysUserService.getOne(Wrappers.lambdaQuery(new SysUser().setUserName(userName)), false);
    }





}
